#include "Math.hpp"
#include "Camera.hpp"
#include "ViewTransform.hpp"
#include <Fl/Fl.H>
#include "gl.hpp"

Camera::Camera(ViewTransform* aTransform)
: mView(aTransform)
{
   SetFocusDistance(5.0f);
   mViewPoint.SetOrigin(Vec3(-5,0,0));
   mViewPoint.PointAt(Vec3(), Vec3(1,0,0), Vec3(0,0,1));
   mView->mRotate = mViewPoint.WorldToLocal();
   mZooming = mPanning = mRotating = false;

   mView->mRotate = mViewPoint.WorldToLocal();
   mView->mTranslate = Mat4();
}

int Camera::handle(int event)
{
   int r(0);
   if (event == FL_PUSH)
   {
      mHasMoved = false;
      mRotating = mPanning = mZooming = 0;
      mDownX = Fl::event_x();
      mDownY = Fl::event_y();
      r = 1;
      if (Fl::event_button1() && !Fl::event_button3())
      {
         mRotating = true;
      }
      else if (Fl::event_button3() && !Fl::event_button1())
      {
         mPanning = true;
      }
      else if (Fl::event_button3() && Fl::event_button1()
         || Fl::event_button2())
      {
         mZooming = true;
      }
   }
   else if (event == FL_RELEASE)
   {
      mHasMoved = false;
      mRotating = false;
      mPanning = false;
      mZooming = false;
      r = 1;
   }
   else if (event == FL_DRAG)
   {
      mHasMoved = true;
      int dx = Fl::event_x() - mDownX;
      mDownX = Fl::event_x();
      float angleX = dx / 200.0f * Math::cPI;
      int dy = Fl::event_y() - mDownY;
      mDownY = Fl::event_y();
      if (mRotating && Fl::event_shift())
      {
         float change = dy * .1f;
         if (change > 0)
         {
            SetFocusDistance(mFocusDistance + change);
         }
         else
         {
            SetFocusDistance(mFocusDistance + change);
         }
         r = 1;
      }
      else if (mRotating)
      {
         float angleY = dy / 200.0f * Math::cPI;
         mViewPoint.RotateAround(mFocusDistance, -angleY, -angleX);
         r = 1;
      }
      else if (mPanning && Fl::event_shift())
      {
         mViewPoint.Move(mViewPoint.Forward() * (-dy * .4f * PanScale()));
         r = 1;
      }
      else if (mPanning)
      {
         mViewPoint.Move(
            mViewPoint.Right() * (-dx * .4f * PanScale())
            + mViewPoint.Up() * (dy * .4f * PanScale()));
         r = 1;
      }
      else if (mZooming)
      {
         float oldFocus = mFocusDistance;
         float newFocus(mFocusDistance);
         //float level = (-dy * .4f * PanScale()) / 10.0f;
         //float change = -dy / 100;
         float level = -dy / 100.0;
         if (level > 0)
         {
            newFocus *= 1+level;
         }
         else
         {
            newFocus /= 1+(-level);
         }
         newFocus += level*.1;
         SetFocusDistance(newFocus);
         float change = oldFocus - mFocusDistance;
         mViewPoint.Move(mViewPoint.Forward() * change);
         r = 1;
      }
   }
   if (r)
   {
      UpdateView();      
   }
   return r;
}

void Camera::UpdateView()
{
   mView->mRotate = mViewPoint.WorldToLocal();
   mView->mTranslate = Mat4();
}

namespace
{
   void DrawCirlce()
   {
      glBegin(GL_LINE_STRIP);

      for (int i = 0; i < 100; ++i)
      {
         float a = i / 99.0f * Math::cTWO_PI;
         Vec3 offset(sin(a), cos(a), 0);
         gl::Vertex(offset);
      }
      glEnd();
   }
}
void Camera::Draw()
{
   if (!(mRotating || mPanning || mZooming)) return;
   if (!mHasMoved) return;
   glColor3f(.5,.5,.5);
   glPushMatrix();
   const float cRADIUS = 1.0;

   Vec3 center(mViewPoint.Origin() + mViewPoint.Forward() * mFocusDistance);
   gl::NoLighting state2;
   gl::Translate(center);
   {
      gl::NoDepthTest state1;
      DrawCirlce();
      glRotatef(90.0f, 0,1,0);
      DrawCirlce();
      glRotatef(90.0f, 1,0,0);
      DrawCirlce();
   }
   {
      glColor3f(0,0,0);
      glLineWidth(2);
      DrawCirlce();
      glRotatef(90.0f, 0,1,0);
      DrawCirlce();
      glRotatef(90.0f, 1,0,0);
      DrawCirlce();
      glLineWidth(1);
   }

   glPopMatrix();
}

void Camera::SetFocusDistance(float aDistance)
{
   mFocusDistance = aDistance;
   if (mFocusDistance < .001) mFocusDistance = .001;
   if (mFocusDistance > 1.0E4) mFocusDistance = 1.0E4;
   mView->mNearPlane = std::max(std::min(10000.0f, mFocusDistance / 10.0f), .005f);
   mView->mFarPlane = std::max(std::min(20000.0f, mFocusDistance * 100.0f), 10.0f);
}
